/*
 * N. Dumont, TAI Lab
 *
 * Class PubSubXMPPManager created on 18 mai 2011
 *
 * Copyright (c) 2009 Thales Communications
 * 160 bd de Valmy - BP82 92704 COLOMBES cedex - France
 * All rights reserved.
 *
 */
package org.diyse.gateway.pubsub.xmpp;

import java.util.HashMap;
import java.util.Map;

import org.apache.felix.ipojo.annotations.Component;
import org.apache.felix.ipojo.annotations.Invalidate;
import org.apache.felix.ipojo.annotations.Property;
import org.apache.felix.ipojo.annotations.Provides;
import org.apache.felix.ipojo.annotations.Requires;
import org.apache.felix.ipojo.annotations.Validate;
import org.apache.felix.ipojo.handlers.event.Subscriber;
import org.diyse.application.IDiYSEApplicationManager;
import org.diyse.application.IEventSubscriber;
import org.diyse.gateway.pubsub.xmpp.internal.ItemEventListenerEventAdmin;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.pubsub.AccessModel;
import org.jivesoftware.smackx.pubsub.ConfigureForm;
import org.jivesoftware.smackx.pubsub.FormType;
import org.jivesoftware.smackx.pubsub.Item;
import org.jivesoftware.smackx.pubsub.ItemPublishEvent;
import org.jivesoftware.smackx.pubsub.LeafNode;
import org.jivesoftware.smackx.pubsub.NodeType;
import org.jivesoftware.smackx.pubsub.PayloadItem;
import org.jivesoftware.smackx.pubsub.PubSubManager;
import org.jivesoftware.smackx.pubsub.PublishModel;
import org.jivesoftware.smackx.pubsub.SimplePayload;
import org.jivesoftware.smackx.pubsub.listener.ItemEventListener;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;

/**
 * @author tai
 * 
 *         Do the mapping between EventAdmin event bus of OSGi, and XMPP PubSub
 *         extension (XEP-0060). When an event (OSGi) is published into a child
 *         topic of diyse root topic, it is sent to the corresponding XMPP
 *         PubSub node. It is possible to subscribe/unsubscribe to XMPP nodes,
 *         in order to inject the corresponding event (OSGi) into EventAdmin
 *         bus.
 * 
 * 
 */
@Component(propagation = true, immediate = true)
@Provides
public class PubSubXMPPManager implements IEventSubscriber {

	@SuppressWarnings("unused")
	@Property(mandatory = true)
	private String login;

	@SuppressWarnings("unused")
	@Property(mandatory = true)
	private String password;

	@Requires
	private EventAdmin m_eventAdminService = null;

	private String m_host = "127.0.0.1";
	private int m_port = 5222;

	private XMPPConnection m_connection = null;
	private PubSubManager m_pubSubManager = null;

	@SuppressWarnings("unused")
	@Validate
	private void start() {
		println("Starting...");
		ConnectionConfiguration config = new ConnectionConfiguration(m_host, m_port);
		m_connection = new XMPPConnection(config);

		try {
			m_connection.connect();
			m_connection.login(login, password);

			m_pubSubManager = new PubSubManager(m_connection);
		} catch (XMPPException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		println("Started.");
	}

	@SuppressWarnings("unused")
	@Invalidate
	private void stop() {

		m_connection.disconnect();
		println("Stopped.");
	}

	/**
	 * Subscribed to eventAdmin diyse root topic. When an event is received, it
	 * is publish to the corresponding XMPP node. The node is created if needed,
	 * and it is named with the same name as the topic in which the event was
	 * sent.
	 * 
	 * @param evt
	 */
	@Subscriber(name = "eventAdminSub", topics = IDiYSEApplicationManager.TOPIC_PREFIX + "*")
	public void receive(Event evt) {
		try {
			Boolean l_isRemote = (Boolean) evt.getProperty(IDiYSEApplicationManager.REMOTE_EVENT);
			// if event is remote, no need to send it to XMPP
			if (l_isRemote != null && l_isRemote) {
				return;
			}
		} catch (Throwable t) {
			t.printStackTrace();
		}

		String type = (String) evt.getProperty(IDiYSEApplicationManager.DATA_TYPE_KEY);
		Object value = evt.getProperty(IDiYSEApplicationManager.DATA_KEY);
		String msg = buildXmlMsg(type, value);

		publishToXMPP(evt.getTopic(), msg);
	}

	/**
	 * @param type
	 * @param value
	 */
	private String buildXmlMsg(String type, Object value) {
		StringBuilder l_sb = new StringBuilder();
		l_sb.append("<data type='");
		l_sb.append(type);
		l_sb.append("' value='");
		l_sb.append(value);
		l_sb.append("'/>");

		return l_sb.toString();
	}

	private void publishToXMPP(String topic, String msg) {
		LeafNode node = getNode(topic);
		try {
			System.out.println("Sending: " + msg);
			PayloadItem<SimplePayload> payload = new PayloadItem<SimplePayload>(new SimplePayload("data", null, msg));
			node.send(payload);
			// node.publish(payload);
		} catch (XMPPException e) {
			e.printStackTrace();
		}

	}

	private LeafNode getNode(String nodeId) {
		LeafNode result = null;

		try {
			result = (LeafNode) m_pubSubManager.getNode(nodeId);
			println("Node already exist (" + nodeId + ")");
		} catch (XMPPException e1) {
			System.out.println(e1.getMessage());
			e1.printStackTrace();
		}

		try {
			if (result == null) {
				result = (LeafNode) m_pubSubManager.createNode(nodeId, getNodeConfiguration());

				// result.addItemEventListener(new ItemEventListener<Item>() {
				//
				// public void handlePublishedItems(ItemPublishEvent<Item> arg0)
				// {
				// System.out.println("Received something");
				// System.out.println(arg0.getItems());
				//						
				// for (Item i : arg0.getItems()) {
				// System.out.println("    "+i.toXML());
				// }
				//						
				// }
				// });

				System.out.println("New node created: " + nodeId);
			}
		} catch (XMPPException e) {
			e.printStackTrace();
		}

		// result.addItemEventListener(
		// new ItemEventListener<Item>() {
		// public void handlePublishedItems(ItemPublishEvent<Item> arg0) {
		// System.out.println("item received:" + arg0);
		// PayloadItem p = (PayloadItem) arg0.getItems().get(0);
		// System.out.println(p.toXML());
		// }
		// });
		return result;
	}

	private Map<String, ItemEventListenerEventAdmin> m_listeners = new HashMap<String, ItemEventListenerEventAdmin>();

	/**
	 * 
	 * @param nodeName
	 *            Name of the node = eventAdmin topic
	 */
	public void subscribeToXMPPNode(final String nodeName) {
		LeafNode l_node;
		try {
			l_node = (LeafNode) m_pubSubManager.getNode(nodeName);
		} catch (XMPPException e) {
			e.printStackTrace();
			return;
		}

		if (m_listeners.containsKey(nodeName)) {
			println("Already subscribed to " + nodeName);
			return;
		}

		ItemEventListenerEventAdmin l_listener = new ItemEventListenerEventAdmin(nodeName, m_eventAdminService);

		m_listeners.put(nodeName, l_listener);
		l_node.addItemEventListener(l_listener);
		try {
			l_node.subscribe(login + "@granville");
		} catch (XMPPException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		println("Subscribed to XMPP node " + nodeName);
	}

	public void unsubscribeToXMPPNode(final String nodeName) {
		if (!m_listeners.containsKey(nodeName)) {
			return;
		}

		LeafNode l_node;
		try {
			l_node = (LeafNode) m_pubSubManager.getNode(nodeName);
		} catch (XMPPException e) {
			e.printStackTrace();
			return;
		}

		ItemEventListenerEventAdmin l_listener = m_listeners.get(nodeName);
		l_node.removeItemEventListener(l_listener);
		m_listeners.remove(nodeName);

		println("Unsubscribed to XMPP node " + nodeName);
	}

	private ConfigureForm getNodeConfiguration() {
		ConfigureForm form = new ConfigureForm(FormType.submit);
		form.setAccessModel(AccessModel.open);
		form.setPublishModel(PublishModel.open);

		form.setDeliverPayloads(true);
		form.setPersistentItems(false);

		form.setPresenceBasedDelivery(false);

		form.setNotifyConfig(false);
		form.setNotifyDelete(false);
		form.setNotifyRetract(false);

		form.setNodeType(NodeType.leaf);
		form.setSubscribe(true);

		return form;
	}

	/**
	 * @param string
	 */
	private void println(String string) {
		System.out.println("[EventAdmin <==> XMPP PubSub] " + string);
	}
}
